/**
 * SFFS - Shift Flash File System 滑动式 FLASH 文件系统
 *
 * 这是一个非常简单的平面文件系统，只适用于地址直接映射到 MCU 地址空间的 RAM/FLASH (如 NOR Flash)，文件之间没有间隙，也不会产生文件碎片，以最大程度上节省空间；
 * 没有文件分配表，文件信息跟数据存储在同一个数据块中，所有的文件按从前到后的顺序遍历
 * 创建文件时，会在最后面创建；文件一旦创建并完成数据写入，就只允许在最后一个文件后面添加数据
 * 删除文件时，后面的文件会向前整体移动，以消除间隙(删除文件前应用层取得的文件指针可能会在删除文件后变得无效！)；
 * 刚通过 sffs_create_file() 创建的文件并不真实存在于 SFFS 中，只是一个 RAM 中的数据结构，此时 fhead = sffs->cache, fblock = NULL
 * 只有通过 sffs_file_append() 写入数据并且数据量超过一页，或 sffs_file_append() 结束 (final = true) 之后才真正写入到 FLASH。此时 fhead = fblock = 文件在 Flash 中的位置
 *
 * 张勇 41204@qq.com
 */
 
#pragma once

#include <stdint.h>
#include <stdbool.h>
#include "std_err.h"

/// 文件名最大长度
#define SFFS_FILENAME_SIZE 15
// 有效文件标志
#define SFFS_FILE_TAG 0x5A

// 文件标志
typedef enum _sffs_flag {
    SFFS_FLAG_NONE     = 0x00, // 普通文件
    SFFS_FLAG_SYSTEM   = 0x01, // 系统文件，不可删除
    SFFS_FLAG_READONLY = 0x02, // 只读文件，不可修改
	SFFS_FLAG_MAX
} sffs_flag_t;

// 文件头缓冲区数据结构
#pragma pack(push, 4)
typedef struct _sffs_file_hcache {
	// 文件有效性标志(FLASH 擦除之后为 0xFF，只有当此标志为 SFFS_FILE_TAG 时才表示是一个合法的文件头)
	uint8_t TAG;
	// 文件标志
    sffs_flag_t flag:8;
	// 保留
	uint16_t _res;
	// 文件名
	char name[SFFS_FILENAME_SIZE + 1];
	// 文件时间戳(自 1970-1-1 00:00:00 到创建时的秒数)
	uint32_t timestamp;
	// 文件数据长度
	uint32_t len;
	// 文件数据指针，指向 Flash 中实际的数据存储地址
	uint8_t *pdata;
} sffs_file_hcache_t;
#pragma pack(pop)

/// 文件系统
typedef struct {
	// FLASH 的起始地址
	uint32_t flashBaseAddr;
	// FLASH 页大小
	uint32_t pageSize;
	// 文件系统在 FLASH 中的存储起始地址(必须为整页起始地址)
	uint32_t startAddr;
	// 文件系统存储区大小(必须为整页)
	uint32_t totalSize;

    // 用于存放当前正在进行 append 操作的文件的头缓冲区
    sffs_file_hcache_t hcache;

    // 文件系统页缓存指针(必须由应用分配并提供)
    uint8_t *dcache;
    // 页缓存中当前已有数据长度
    uint32_t dclen;

	/// FLASH 擦除函数
	/// @param offset 在 FLASH 中偏移地址(必须是整页地址)
	/// @param size 擦除长度(必须是页大小的整数倍)
	void (*erase)(uint32_t offset, uint32_t size);
	/// FLASH 写入函数
	/// @param offset 在 FLASH 中偏移地址(必须是整页地址)
	/// @param len 数据长度(必须是页大小的整数倍)
	/// @param data 数据
	void (*write)(uint32_t offset, uint32_t len, uint8_t *data);
} sffs_t;

// forward 声明
typedef struct _sffs_file sffs_file_t;


#ifdef __cplusplus
extern "C" {
#endif

//
// 关于 SFFS 文件系统管理的 API
//
/// @brief 计算文件系统中剩余的空间可存放一个多大的文件(指净荷)
uint32_t sffs_get_free_size(const sffs_t *sffs);
/// @brief 遍历文件系统，获取下一个文件
/// @param prev 上一个文件指针，如果从头开始遍历，则 prev = NULL
/// @return 文件指针。如果没有更多文件了，则返回 NULL。
sffs_file_t* sffs_next_file(const sffs_t *sffs, sffs_file_t *prev);
/// @brief 获取文件数量
uint32_t sffs_get_file_count(const sffs_t *sffs);
/// @brief 通过文件名查找文件
sffs_file_t* sffs_find_file(const sffs_t *sffs, const char *name);
/// @brief 删除文件
std_err_t sffs_delete_file(const sffs_t *sffs, sffs_file_t *file);
/// @brief 删除从 idx(从1开始计数) 开始及之后的所有文件
std_err_t sffs_truncate(const sffs_t *sffs, uint32_t idx);

//
// 关于 SFFS 文件读写的 API
//
/// 创建文件
/// @return 指向页缓存中的文件头指针，必须通过 sffs_file_append() 向文件中添加数据后，才会真正保存到 Flash
/// @note 当前还有其它未关闭的文件正在使用页缓冲区，则不允许再次创建文件
sffs_file_hcache_t* sffs_create_file(sffs_t *sffs, const char *name);

/// 向文件后面添加数据(只有最后一个文件才允许在后面添加数据)
/// @param file 文件指针
/// @param data 本次要保存的数据
/// @param len 本次要保存的数据长度
/// @param final 写入是否结束
/// @param 实际保存的数据长度，final==false 时只会在凑够一整页时才会真正地保存，不足一页时会暂时放在缓存里；final==true 时会强制保存
uint32_t sffs_file_append(sffs_t *sffs, sffs_file_hcache_t* file, void *data, uint32_t len, bool final);

/// 修改文件数据(数据范围不允许超过文件当前的存储空间)
/// @param file 文件指针
/// @param offset 文件数据偏移地址(相对于此文件的存储首地址)
/// @param data 本次要保存的数据
/// @param len 本次要保存的数据长度
/// @param 实际保存的数据长度
uint32_t sffs_file_modify(sffs_t *sffs, sffs_file_t* file, uint32_t offset, void *data, uint32_t len);


#ifdef __cplusplus
}
#endif
